/* All scrmgr password checking */

/* compile generator program with:

Linux:
gcc -DSM_SYSV -DMAIN -I/usr/local/include -L/usr/local/lib smmaps.c smstubio.o smlib.o -lmylib -o smmaps

SunOS:
gcc -DSM_SUNOS -DMAIN -I/usr/local/include -L/usr/local/lib smmaps.c smstubio.o smlib.o -lmylib -o smmaps

   ... or in TurboC++ 1.0 :

tcc -ml -g0 -G -N- -DSM_TURBOC -DMAIN -c -I/tc/include smmaps.c 
tcc -ml -g0 -G -N- -L/tc/lib -osmmaps.exe smmaps.obj smstubio.obj smlib.obj mylibl.lib
del smmaps.obj

NEEDS TO BE CHANGED TO HAVE OPTION -h hostname!!! to compile for another platform!!!!

*/

/* Password file:
   ./.sminitrc
   ./sminit.rc
   ~/.sminitrc or
   /etc/sminit.rc

or, under MSDOS: 

   ./sminit.rc or 
   PRG_DIRECTORY/sminit.rc or
   /etc/sminit.rc

   Contents of password file:
   	# comments
   	Expire Time (long seconds)
	Password
	[Demo]

    Check:
    	Time stamp on password file <= current time
	Time stamp on input file <= current time (mapping1)
	Password hashes correctly
	Expire time > current time 
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <getopt.h>
#define SM_CLASS extern
#include "smlib.h"
#if HAS_UNAME
#include <sys/utsname.h>
#include <unistd.h>
#endif

#define create_password createp
#define DOS_NAME "DOS"

static char fail_msg[] = 
    "Initialisation error on file '%s' - contact your supplier";

static char no_file_msg[] = 
#if NEEDS_PATH_FOR_PROGS
    "Can't find sminit.rc";
#else
    "Can't find ~/.sminitrc or /etc/sminit.rc";
#endif

static struct {
    int		is_demo;
    int		days_left;
} global;

static void init_abort ARG3(CHAR *, msg, int, code, CHAR *, filename)
{
    CHAR	*buf = alloca(200);
    
    sprintf(buf, msg, filename);
    fprintf(stderr, "%s: code %d\n", buf, code);
    exit(1);
}

/* Check that a file to be opened under SCRMGR has not had its timestamps munged */
int sm_mapping1 ARG1(CHAR *, filename)
{
    struct stat	statbuf;
    time_t	now;
    CHAR	*buf = alloca(200);

    if (stat(filename, &statbuf) == 0) {

	now = time(NULL);

	if (statbuf.st_mtime > now) {
	    sprintf(buf, fail_msg, filename);
	    fprintf(stderr, "%s: code %d\n", buf, 10);
	    return(1);
	}
    }
    return(0);
}

static void trim ARG1(char*, buf)
{
    char	*p;

    if ((p = strchr(buf, '\n')) != NULL)
	*p = 0;
    if ((p = strchr(buf, ' ')) != NULL)
	*p = 0;
    if ((p = strchr(buf, '\t')) != NULL)
	*p = 0;
}

#define PASSWD_LEN 12
/* PASSWD_LEN characters from A-Z */
static CHAR *create_password ARG3(CHAR *, 	check_password, 
				  CHAR *,	expire_time,
				  CHAR *,	id)
{
    int		key = 0, i;
    char	*c, *d;
    static char	hostname[80];

    trim(check_password);
    trim(expire_time);

    hostname[0] = 0;
    if (id && *id) {
	strcpy(hostname, id);
	trim(hostname);
    } else {
#if HAS_UNAME
	struct utsname buf;

	uname(&buf);
	strcpy(hostname, buf.nodename);
#elif HAS_GETHOSTNAME
	gethostname(hostname, 80);
#else /* MS-DOS? */
	strcpy(hostname, DOS_NAME);
#endif
    }

    for (i = 0; i < PASSWD_LEN; i++) {
	for (c = expire_time; *c; c++)
	    if (hostname[0] && strcmp(hostname, DOS_NAME))
		for (d = hostname; *d; d++)
		    key = 17 * key + 31 * toupper(*c) + 41 * toupper(*d);
	    else
		key = 17 * key + 31 * toupper(*c);
	check_password[i] = 'A' + abs(key % 26);
    }
    check_password[PASSWD_LEN] = 0;
    trim(check_password);
    return(hostname);
}

/* called by sm_term */
void sm_mapping2()
{
    static char *rudeness[] = {
"Thanks for trying this program! Use beyond the trial period requires",
"licence purchase and registration. Licencing is not optional.",
"This is not free software.",
"",
"To purchase this software (and get rid of this message), contact:",
"",
"         Compilations Ltd.",
"         Fx: +852-849-4801",
NULL
};

    char	**c = rudeness;
    
    while (global.is_demo && c && *c) {
	fputs(*c++, stderr);
	fputc('\n', stderr);
    }

#ifdef IS_SECURE
#define WARNING_PERIOD 14
    if (global.days_left < WARNING_PERIOD)
	fprintf(stderr, 
		"The password for Screen Manager will expire within %d days.\n", 
		global.days_left);
#endif
}

/* read .sminitrc file, check password & other paranoia */
CHAR *sm_mapping3 ARG1(CHAR *, password_filename)
{
#define LINESIZE 100
    static char	filename[LINESIZE];
    char	*p;
    int		file = -1;
    FILE	*f;
    struct stat	statbuf;
    time_t	now;
    char	*expire_time_buf = alloca(LINESIZE);
    time_t	expire_time;
    char	*password = alloca(LINESIZE);
    char	*check_password = alloca(LINESIZE);
    char	*input_buf = alloca(LINESIZE);

    if (password_filename && *password_filename) {
	strcpy(filename, password_filename);
	file = open(filename, O_RDONLY);
    } else {

#if NEEDS_PATH_FOR_PROGS
	strcpy(filename, "sminit.rc");
	file = open(filename, O_RDONLY);
	if (file == -1) {
	    sprintf(filename, "%ssminit.rc", sm_program_directory);
	    file = open(filename, O_RDONLY);
	}
#else 
	strcpy(filename, ".sminitrc");
	file = open(filename, O_RDONLY);

	if (file == -1) {
	    strcpy(filename, "sminit.rc");
	    file = open(filename, O_RDONLY);
	}

	if (file == -1) {
	    p = getenv("HOME");
	    if (p && *p) {
		strcpy(filename, p);
		if (filename[strlen(filename) - 1] != '/')
		    strcat(filename, "/");
		strcat(filename, ".sminitrc");
		file = open(filename, O_RDONLY);
	    }
	}

#endif

	if (file == -1) {
	    strcpy(filename, "/etc/sminit.rc");
	    file = open(filename, O_RDONLY);
	}
    }

#ifdef IS_SECURE
    if (file == -1)
	init_abort(no_file_msg, 1, filename);

    if (fstat(file, &statbuf))
	init_abort("Can't stat the SCRMGR init file '%s'.", 2, filename);

    close(file);
    file = open(filename, O_WRONLY);
    if (file == -1)
	init_abort("Can't open the SCRMGR init file '%s' for writing", 3, filename);

    close(file);
    f = fopen(filename, "r");
    if (f == NULL) 
	init_abort(no_file_msg, 4, filename);

    now = time(NULL);

    if (statbuf.st_mtime > now)
	init_abort(fail_msg, 5, filename);

    /* Read the password file in */
    do {
	expire_time_buf[0] = 0;
	if (fgets(expire_time_buf, LINESIZE, f) == NULL)
	    init_abort(fail_msg, 6, filename);
    } while (expire_time_buf[0] == '#');

    do {
	password[0] = 0;
	if (fgets(password, LINESIZE, f) == NULL)
	    init_abort(fail_msg, 7, filename);
    } while (password[0] == '#');

    sm_toupper(password);
    do {
	input_buf[0] = 0;
	if (fgets(input_buf, LINESIZE, f) == NULL)
	    break;
    } while (*input_buf == '#');

    sm_toupper(input_buf);
    trim(input_buf);
    if (strcmp(input_buf, "DEMO") == 0)
	global.is_demo = 1;
    else
	global.is_demo = 0;

    expire_time = atol(expire_time_buf);
    if (expire_time < now)
	init_abort(fail_msg, 8, filename);

    trim(password);
    trim(expire_time_buf);
    create_password(check_password, expire_time_buf, NULL);
    if (strcmp(check_password, password))
	init_abort(fail_msg, 9, filename);

    fclose(f);
    utimes(filename, NULL); /* set timestamp on password file */

    global.days_left = (expire_time - now) / (3600 * 24);
    return(filename);
#else
    return(filename);
#endif /* IS_SECURE */
}				   

#ifdef MAIN

#include <setjmp.h>
jmp_buf		NoMoreHeap;
CHAR *smmaps_rcsid(void) {
    return("$Id: smmaps.c,v 1.3 2008/02/19 13:15:50 bhepple Exp $");
}

static CHAR *usage_msg[] = {
"Creates SCRMGR passwords. Not for distribution. Strictly Compilations only.",
"",
"YY/MM/DD   : Year, month & day to expire (12am).",
#ifndef SM_TURBOC
"hostid     : of target.",
#endif
"-c         : just checks the current password",
"-f filename: use 'filename' as the password file",
NULL
};

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, 
	    "Usage: smmaps [-f filename ] [ -c | YY/MM/DD [ hostid | %s ] ]\n", DOS_NAME);
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

#ifdef NO_STRPTIME
char *strptime ARG3(char *, buf, char *, fmt, struct tm *, tm)
{
    /* Ignore fmt, assume YY/MM/DD and only fill in YMD in tm - in other words
       a hack for these purposes and MS-DOG only */

    tm->tm_mday = atoi(&buf[6]);	/* 1-31 */
    tm->tm_mon = atoi(&buf[3]) - 1;	/* 0-11 */
    tm->tm_year = atoi(buf);		/* year - 1900 */
}
#endif

#ifdef NO_TIMEGM
/* Seconds since 00:00 Jan 1 1970 GMT - or thereabouts! */
time_t timegm ARG1(struct tm *, tm)
{
    /* Only look at MDY in tm as strptime above - a hack */
    time_t 	num_years, num_days, num_secs;
    static int	mday[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
		       /* J   F   M   A    M    J    J    A    S    O    N    D */

    num_years = tm->tm_year - 70;
    num_days = num_years * 365;
    num_days += (num_years + 1) / 4; /* previous leap years */
    if (num_years > 30)
	num_days -= 1; /* 2000 is not a leap year - don't care about 2100! */

    num_days += mday[tm->tm_mon];

    /* is this year a leap? */
    if (tm->tm_mon > 1) { 
	time_t 	i = (num_years + 2) / 4;
	
	i *= 4;
	if ((i == (num_years + 2)) && num_years != 30)
	    num_days += 1;
    }

    num_days += (tm->tm_mday - 1);

    num_secs = num_days * 24 * 60 * 60;
    return(num_secs);
}
#endif /* NO_TIMEGM */

main(argc, argv)
    int		argc;
    char	**argv;
{
    char	*passwd = alloca(LINESIZE), *testfilename = NULL;
    struct tm	tmbuf;
    time_t	expire_time, now, days;
    char	*expire_time_buf = alloca(LINESIZE), *hostname;
    int		c, justCheck = 0;

    sm_pre_init(argc, argv);

    while ((c = getopt(argc, argv, "f:c?")) != EOF)
        switch (c) {
	case 'f': 
	    testfilename = optarg; 
	    break;

	case 'c':
	    justCheck = 1;
	    break;

        default:  
	    usage(); 
	    break;
        }

    if (argc == 1)
	justCheck = 1;

    if (justCheck) {
	testfilename = sm_mapping3(testfilename);
	fprintf(stderr, "Installed init file '%s' is ok.\n", testfilename);
	sm_mapping2();
	exit(0);
    }

    strptime(argv[1], "%y/%m/%d", &tmbuf);
    expire_time = timegm(&tmbuf);
    sprintf(expire_time_buf, "%ld", expire_time);
    hostname = create_password(passwd, expire_time_buf, argv[2]);

    puts(expire_time_buf);
    puts(passwd);
    if (hostname && *hostname)
	puts(hostname);
    fflush(stdout);

    now = time(NULL);
    days = (expire_time - now) / 3600;
    days /= 24; /* do this separately as 16 bit machines convert 3600*24 to a 
		   16bit integer and _then_ divide!! */
    fprintf(stderr, "About %ld days\n", days);
}
#endif /* MAIN */

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
